/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/** 
 * \file
 * \brief Base class for SQL interface extension - header.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#ifndef _MACI_INTERFACES_SQL_SQLEXTENSION_HPP_
#define _MACI_INTERFACES_SQL_SQLEXTENSION_HPP_

#include "SQLQuery.hpp"
#include "SQLResult.hpp"
#include "SQLClient.hpp"
#include <string>
#include <map>


namespace MaCI {
  namespace SQL {

    /** Base class for building SQL Client extensions.
     */
    class CSQLExtension
    {
    public:

      /** Get last error.
       *
       * This function returns the last stored error from the class
       * instance.
       *
       * @return                Non-empty string if error occured during
       *                        last call, otherwise empty string.
       */
      const std::string &GetLastError(void) const;
      

      /** Assign used SQL client instance.
       * 
       * This function assigns the SQL Client instance to be used.
       * 
       * @param[in] aSQLClient SQL Client to use for communication.
       */
      void SetSQLClient(CSQLClient *aSQLClient);


      /** Assign default target table.
       *
       * This function stores a default target table for this
       * extension class.
       * 
       * @param[in] aTargetTable Targettable name.
       */
      void SetTargetTable(const std::string &aTargetTable);


      
      /** Execute Query with given 'WHERE' clause.
       *
       * This function constructs a query and returns the converted
       * result set based on the given WHERE clause. The string
       * given to 'aWhereClause' is fed directly to the database
       * engine, hence giving endless possibilities on reading table
       * contents.
       *
       * @param[out] aDecodedResult  Content-Converted result set is stored here.
       * @param[in] aWhereClause SQL 'WHERE' clause to control 
       *            the selection results. This can be arbitrary long and
       *            complex string as required. If this is empty, no
       *            where clause is generated, resulting in full table dump
       *            (be careful)
       * @param[in] aTimeout_ms Timeout in milliseconds to wait for the 
       *            Query to complete.
       * @return    'true' if query was OK, and a result set was stored.
       *            query can be OK even if result set is empty, this 
       *            only means that no data is available with given
       *            where clause.
       */
      template <class T> 
      bool ExecuteSelectQuery(std::vector<T> &aDecodedResult,
                              const std::string &aWhereClause,
                              const unsigned int aTimeout_ms = 5000);


    protected: // Functions
      CSQLExtension(void)
        : iSQLClient(NULL),
          iTargetTable(),
          iLastError() {}
      
      CSQLExtension(CSQLClient *aSQLClient, 
                    const std::string &aTargetTable = "") 
        : iSQLClient(aSQLClient),
          iTargetTable(aTargetTable),
          iLastError() {}

      virtual ~CSQLExtension(void) {}


      /** Set last error.
       *
       * Setting empty string clears error.
       *
       * @param[in] aLastError   Errorstring to set.
       */
      void SetLastError(const std::string &aLastError);


      /** Decode a SQLResult table as type 'T'.
       *
       * This function attempts to decode a SQL result set of 
       * rows to a vector of elements of type 'T'. Type T must
       * have a method:
       * \code
       * bool DecodeSQLResultRow(const TSQLRow &)
       * \endcode
       *
       * @param[out] aDecodedResult Succesfully Converted elements are
       *                            stored in this reference.
       * @param[in] aSQLResult SQLResult class containing the SQL interface reply.
       * @return              'true' if ALL elemens were correctly converted.
       *                      'false' if any have failed. The array contains
       *                      all rows which were succesfully converted before
       *                      encountering an error.       
       */
      template <class T> 
      static bool DecodeSQLResultTable(std::vector<T> &aDecodedResult,
                                       const CSQLResult &aSQLResult);
      
      
    protected: // Members
      CSQLClient *iSQLClient;   ///< Instance of CSQLClient to use
      std::string iTargetTable; ///< Default targetTable.
      std::string iLastError;   ///< Last error, Zero-length if last call was succesfull.
    private:
      CSQLExtension(const CSQLExtension &)
        : iSQLClient(),
          iTargetTable(),
          iLastError() {}
      CSQLExtension &operator=(const CSQLExtension &) { return *this; }
    };
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////// Implementation //////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////

    void CSQLExtension::SetTargetTable(const std::string &aTargetTable) {
      iTargetTable = aTargetTable;
    }
    ///////////////////////////////////////////////////////////////////////////

    void CSQLExtension::SetLastError(const std::string &aLastError) {
      iLastError = aLastError;
    }
    ///////////////////////////////////////////////////////////////////////////
    
    const std::string &CSQLExtension::GetLastError(void) const {
      return iLastError;
    }
    ///////////////////////////////////////////////////////////////////////////

    void CSQLExtension::SetSQLClient(CSQLClient *aSQLClient) {
      iSQLClient = aSQLClient;
    }
    ///////////////////////////////////////////////////////////////////////////

    template <class T> 
    bool CSQLExtension::ExecuteSelectQuery(std::vector<T> &aDecodedResult,
                                           const std::string &aWhereClause,
                                           const unsigned int aTimeout_ms) {
      bool result = false;
      
      if (iTargetTable.size()) {
        // Base Query string
        std::string querystring("SELECT * FROM " + 
                                iTargetTable);
        
        // Is there a 'WHERE' clause?
        if (aWhereClause.size()) {
          // Got Where clause. So append it to query.
          querystring += std::string(" WHERE ") + aWhereClause;
        }
          
        //        dPrint(ODTEST,"Query: '%s'", querystring.c_str());
            
        // Construct CSQLQuery directly.
        const CSQLQuery query(querystring);
            
        // Execute query!
        CSQLResult sqlresult;
        if (iSQLClient->ExecuteQuery(sqlresult, query, aTimeout_ms)) {
          if (sqlresult.GotTable()) {
            // Query OK!
            iLastError.clear();
            result = DecodeSQLResultTable(aDecodedResult, sqlresult);
              
          } else if (sqlresult.GotError()) {
            // Query Failed! :(
            iLastError = sqlresult.GetLastError();
              
          } else {
            iLastError = "Unknown error!";

          }
            
        } else {
          iLastError = "Query failed or timed out";
          dPrint(ODWARN,"Query failed or timed out :(");
            
        }
          
      } else {
        dPrint(ODWARN,"No TargetTable set! This call would fail, so not doing it.");
          
      }
        
      return result;
    }
    ///////////////////////////////////////////////////////////////////////////
    
    template <class T> 
    bool CSQLExtension::DecodeSQLResultTable(std::vector<T> &aDecodedResult,
                                             const CSQLResult &aSQLResult) {
      // Result.
      bool result = true;
      
      // Clear output.
      aDecodedResult.clear();
      
      // Check that result contains a table.
      if (aSQLResult.GotTable()) {
          
        // Get table reference for easier (faster) access.
        const CSQLTable &t = aSQLResult.GetTable();
          
        // Get rowcount and iterate rows.
        const unsigned int rowcount = t.GetRowCount();
          
        // Pre-Reserve memory for 'rowcount' elements (speedup
        // optimization)
        aDecodedResult.reserve(rowcount);
          
        // Loop through all rows, watch those results!
        for (unsigned int i = 0; 
             i < rowcount && result; 
             ++i) {
          T newelement;
          result &= newelement.DecodeSQLResultRow( t.GetRow(i) );
          if (result) aDecodedResult.push_back( newelement );
        }
          
        // At this point, the result may have changed in the loop if
        // errors were encountered.
      }
        
      return result;
    } 
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////
  }
}

#endif // _MACI_INTERFACES_SQL_SQLEXTENSION_HPP_
